/**
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.apache.lucene.spatial.geometry;

/**
 * Rectangle based on latitude and longitudes
 */
public class LatLngRectangle {

  private final LatLng lowerLeft;
  private final LatLng upperRight;

  /**
   * Creates a new LatLngRectangle with the given lowerLeft latitude/longitude and upperRight latitude/longitude
   *
   * @param lowerLeft Latitude & longitude of the lower left point of the Rectangle
   * @param upperRight Latitude & longitude of the upper right point of the Rectangle
   */
  public LatLngRectangle(LatLng lowerLeft, LatLng upperRight) {
    this.lowerLeft = lowerLeft;
    this.upperRight = upperRight;
  }
  
  public static LatLngRectangle createIncludingLLRect(LatLng center, double radius) {
	  return createIncludingLatLngRectangle( center, radius, DistanceUnits.MILES);
  }

  public static LatLngRectangle createIncludingLatLngRectangle(LatLng center, double radius, DistanceUnits distanceUnit) {
		double minLat;
    double maxLat;
		double minLng;
    double maxLng;
		
		if (radius > center.arcDistance(new LatLng(LatLng.LATITUDE_DEGREE_MAX, 0))) {
			maxLat = LatLng.LATITUDE_DEGREE_MAX;
		} else {
			maxLat = LatLng.computeDestination(center, radius, 0).getLat();
			
		}
		if (radius > center.arcDistance(new LatLng(LatLng.LATITUDE_DEGREE_MIN, 0))) {
			minLat = LatLng.LATITUDE_DEGREE_MIN;
		} else {
			minLat = LatLng.computeDestination(center, radius, 180).getLat();
		}

		if((radius > 2 * Math.PI * distanceUnit.earthRadius() * Math.cos(Math.toRadians(minLat))) ||
        (radius > 2 * Math.PI * distanceUnit.earthRadius() * Math.cos(Math.toRadians(maxLat)))) {
			maxLng = LatLng.LONGITUDE_DEGREE_MAX;
			minLng = LatLng.LONGITUDE_DEGREE_MIN;
		} else {
			maxLng = LatLng.computeDestination(new LatLng(Math.max(Math.abs(minLat), Math.abs(maxLat)), center.getLng()), radius, 90).getLng();
			minLng = LatLng.computeDestination(new LatLng(Math.max(Math.abs(minLat), Math.abs(maxLat)), center.getLng()), radius, 270).getLng();
		}

		return new LatLngRectangle(new LatLng(minLat, minLng), new LatLng(maxLat, maxLng));
	}

  /**
   * Creates a new LatLngRectangle with the given latitude/longitude as its centre, and the given radius as both its width/2
   * and height/2 (creating a square box)
   *
   * @param center Latitude and longitude of the centre of the box
   * @param radius Width/height of the box
   * @return LatLngRectangle representing a box with the given centre and radius
   */
	public static LatLngRectangle createIncludingLLRectByCorner(LatLng center, double radius) {
		final int ANGLE_TO_UPPER_RIGHT_CORNER = 45;
		final int ANGLE_TO_LOWER_LEFT_CORNER = 225;
		final double SIDE_TO_DIAG_SQUARE_RATIO = Math.sqrt(2);

		LatLng upperRightCorner = LatLng.computeDestination(center, radius
				* SIDE_TO_DIAG_SQUARE_RATIO, ANGLE_TO_UPPER_RIGHT_CORNER);
		LatLng lowerLeftCorner = LatLng.computeDestination(center, radius
				* SIDE_TO_DIAG_SQUARE_RATIO, ANGLE_TO_LOWER_LEFT_CORNER);

		return new LatLngRectangle(lowerLeftCorner, upperRightCorner);
	}

  // =============================================== Getter / Setters ================================================

  /**
   * Returns the lower left LatLng
   *
   * @return Lower left LatLng
   */
  public LatLng getLowerLeft() {
    return lowerLeft;
  }

  /**
   * Returns the upper right corner LatLng
   *
   * @return Upper right corner LatLng
   */
  public LatLng getUpperRight() {
    return upperRight;
  }
}
